// SPDX-License-Identifier: Mulan PSL v2
/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * This software is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

use std::sync::atomic::{AtomicUsize, Ordering};

use rustix::param;

/// Aligns `value` upwards to the specified `align` boundary.
///
/// `align` must be a power of two.
///
/// # Panics
/// Panics if `align` is not a power of two, or if the operation overflows.
#[inline(always)]
pub const fn align_up(value: usize, align: usize) -> usize {
    assert!(align.is_power_of_two(), "Align must be a power of two");

    let mask = align - 1;

    match value.checked_add(mask) {
        Some(v) => v & !mask,
        None => panic!("align overflow"),
    }
}

/// Checks if `value` is aligned to the specified `align` boundary.
#[inline(always)]
pub const fn is_aligned(value: usize, align: usize) -> bool {
    (value & (align - 1)) == 0
}

/// Returns the system default page size in bytes.
#[inline]
pub fn page_size() -> usize {
    static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);

    match PAGE_SIZE.load(Ordering::Relaxed) {
        0 => {
            let size = param::page_size();
            // It doesn't matter if multiple threads race here;
            // they will all calculate and store the same system constant.
            PAGE_SIZE.store(size, Ordering::Relaxed);
            size
        }
        size => size,
    }
}

/// Aligns `value` up to the next system page boundary.
#[inline(always)]
pub fn page_align_up(value: usize) -> usize {
    self::align_up(value, self::page_size())
}

/// Checks if `value` is aligned to a system page boundary.
#[inline(always)]
pub fn is_page_aligned(value: usize) -> bool {
    self::is_aligned(value, self::page_size())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_alignment() {
        assert_eq!(align_up(0, 8), 0);
        assert_eq!(align_up(1, 8), 8);
        assert_eq!(align_up(7, 8), 8);
        assert_eq!(align_up(8, 8), 8);
        assert_eq!(align_up(9, 8), 16);

        assert!(is_aligned(16, 8));
        assert!(!is_aligned(15, 8));
    }

    #[test]
    fn test_page_helpers() {
        let ps = page_size();
        assert!(ps > 0);
        assert!(ps.is_power_of_two());

        assert_eq!(page_align_up(1), ps);
        assert_eq!(page_align_up(ps), ps);
        assert!(is_page_aligned(ps * 2));
    }
}
